-
Notifications
You must be signed in to change notification settings - Fork 92
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP: GPU basic rendering support, renderer lib #517
Conversation
1e00bdd
to
0580afd
Compare
+1 for renaming core namespace to 2d. Also little bit confusing about file layout, I prefer to group render files based on used library not by render type, so I mean put everything related to sokol under sokol root directory:
Moreover I think we should try to move SokolRenderer as 3rd plugin library (so/dll), because this helps to understand which parts are still coupled with legacy renderer, and solve problem with dependencies on sokol-gfx, handmath. In current version, Sokol[2D|Abstract]Renderer is hard coupled with Soko[|3D]Renderer, you can't use Sokol3DRenderer without Sokol2DRenderer because sokolg-gfx initialization happens in Sokol2DRenderer. So, probably we should remove Sokol2DRenderer from game core, and replace it with Dummy2DRenderer or SDL_renderer as before (?). When you load the renderer (so/dll) library you will get two pointers: to 2DRenderer (can be null), 3DRenderer and apply them to game. If library does not implement 2DRenderer (rust case) then Dummy/SDL_renderer is applied in other case 2d renderer from library will work. I am not sure should we support mixing renderers like Sokol2D + Rust3D, I think we can assume that rendering library must provide 3d renderer and optionally can provide 2d renderer. So, in game core we will have hardcoded 2D renderer and no 3d renderers at all (only dummy one), this will make our live easier and we can no worry about mixing 3rd party libraries. Another case that not covered by this PR is a switching between worlds, if we agreed that rust library will load resources by it's own then we need to notify renderer about which world is loaded, and probably we should wait until library says that world is ready. I will continue to look PR next days, great work! -- |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for doing this grand rendering abstraction, and sorry for getting late to review this!
I believe most if not all of my notes are not important. The principle of abstraction makes sense, and we shouldn't hold it in the air until it's perfect. Instead, we should strike for iterating on it rapidly.
What do you see being the next step? Just trying to hook up vange-rs renderer and see if there are any roadblocks?
#include "exception.h" | ||
|
||
namespace renderer { | ||
template<typename TResourceId, typename TResource> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is this TResourceId
at all? Wouldn't it be the same as Resource<TResource>
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it's like that because I'm not totally happy with my current unique integral type approach. By unique integral type I mean that we have integer IDs, but we should not pass a Texture ID to the HeightMap methods for example. So I keep the ResourceStorage accepting any struct with int32_t id
field in case if I change the current approach.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can cross this bridge when we get there. Seems strange to overcomplicate the API for something we don't know, at this point. Your choice though, not a big deal.
return it; | ||
} | ||
|
||
std::unordered_map<TResourceId, std::unique_ptr<TResource>, ResourceIdHash, ResourceIdEquals> _storage; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the unordered map is probably suboptimal here, and something like a freelist would turn out better
but I doubt it matters right now, nothing to worry about
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But would it be way less optimal for id lookup? I mean the idea of the ResourceStorage is to map resource id to the internal resource representation. For example if we have a Light resource and change the light position every frame, and the concrete implementation maps the light id to it's internal light object and updates the position
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, a freelist would mean that ID lookup is just indexing into an array. No hashing, bucketing, etc, involved.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, yes, exactly. I thought it's based on linked list
render_context->bind.vertex_buffers[0] = vbuf; | ||
render_context->vertex_buffer = vbuf; | ||
|
||
uint16_t indices[] = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we are just drawing 2 triangles, can't we avoid index buffer altogether an just have a triangle strip with 4 vertices?
std::unique_ptr<RenderContext> render_context = std::make_unique<RenderContext>(RenderContext{}); | ||
|
||
float vertices[] = { | ||
-1.0f, 1.0f, 0.0f, 0.0f, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: we shouldn't really need this vertex buffer, the values can be produced from within the shader based on gl_VertexID
virtual HeightMap map_create(const MapDescription& map_description) = 0; | ||
virtual void map_destroy(HeightMap map) = 0; | ||
virtual void map_query(HeightMap map, int32_t* width, int32_t* height) = 0; | ||
virtual void map_update_data(HeightMap map, const Rect& rect, uint8_t* height, uint8_t* meta) = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
where is the data to update with?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea is that we copy a slice of data from the lineT
to the height
and meta
and pass them to the map_update_data
. And delete them after the call. So may be the arguments should be const uint8_t*
. I wrote some comments to the AbstractRenderer.h, see my last commits and MapUpdater class
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, yes, these should be const uint8_t*
then
} | ||
|
||
HeightMap::~HeightMap() { | ||
delete[] height_map; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should also delete the meta and palette
Thanks for the review, guys!
Yes, that make sense
Ok, I'll think about the new naming scheme. Any ideas of the better name for the
My main motivation of having the Sokol 2D renderer is that we cannot mix SDL_renderer and direct OpenGL calls (from SDL_GL_CreateContext). I have not found any information of using them in parallel (except of a weird SDL_GL_BindTexture call). I suppose we cannot just use SDL_renderer and vange-rs renderer in parallel, for example. I guess SDL developers don't want anyone to mess with their internal SDL_Renderer context. So I decided that we should have our own OpenGL 2D renderer. My initial idea was that we always have Sokol 2D render as SDL_Renderer replacement and just pass a pointer of the OpenGL context to the concrete Abstract[Scene]Renderer implementation (to the vange-rs lib for example). The only problem here is that the concrete implementation may unintentionally mess with the internal Sokol OpenGL state. Uh, and sokol_gfx doesn't have a way to pass the OpenGL context to it, so the Sokol[Scene]Render just assumes that everything is already initialized.. I like your suggestion that every renderer implementation should provide a scene renderer and a 2D renderer. That could be another solution of the SDL_renderer/OpenGL problem. So I see a few possible options:
This is an important one: the current approach is to pass a world information from Vangers to the Abstract[Scene]Renderer implementation. See virtual void renderer::scene::AbstractRenderer::map_update_data(HeightMap map, const Rect& rect, uint8_t* height, uint8_t* meta) = 0; This method updates a part of the height map with new height and meta information. The idea is that we have an empty height map at the start and update it during the game. The updates will be triggered if the part of the level was loaded from the disk or there was an update in the game world, like roof collapse. And we do not have multiple updates per frame and accumulating them during the frame instead (basically expanding the update region). See MapUpdater class I plan to update every renderer object from the game, like 3D models or sprites. @kvark can we support such approach on the Rust side? @kvark Thanks for your notes :)
Totally agreed
Yes, I think we should focus on the rendering of the height map in game using the vange-rs library. So we should decide what would be an interface of the vange-rs library and probably adapt the AbstractRenderer interface to it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for making those Rusty changes!
typedef typename std::unordered_map<TResourceId, std::unique_ptr<TResource>, ResourceIdHash, ResourceIdEquals>::iterator iterator; | ||
|
||
ResourceStorage() | ||
:_next(0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uses convention that resource id is valid if >= 0, it's not clear thought
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should never use the internal id
field explicitly, so zero should be fine. Anyway I plan to refactor the "resource" system, since it's a bit over engineered..
}; | ||
|
||
enum class BlendMode { | ||
None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not clear the meaning of None, do not blend at all? or copy?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It means Copy, yes. Should it be called "Copy"? What's the usual naming sheme?
// Destroys the HeightMap | ||
virtual void map_destroy() = 0; | ||
|
||
// Updates a rectangular area the selected HeightMap with height data from *height* and meta data from *meta* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
didn't get it, about which pointers this comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's an old comment, thanks. We use now lineT
from MapDescription.
@@ -0,0 +1,82 @@ | |||
#ifndef VANGE_RS_H |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what about extern "C" for all functions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But there is already extern "C", see #ifdef __cplusplus
|
||
uint8_t* _lineT_r = lineT[iy + y_start]; | ||
if(_lineT_r != nullptr){ | ||
std::memcpy(height_map_r, _lineT_r + x_start, sizeof(uint8_t) * width); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why we need to do memcpy, why not to pass original pointers?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think one shouldn't use SokolRenderer now in general, because it was used only as simplified version for Rust renderer, like a stub before we've got the Rust renderer.
The MapUpdater used an old approach, where I passed the map updates via region copies
src/CMakeLists.txt
Outdated
${WIN_LIB} | ||
${STEAM_LIBS} | ||
xtool | ||
xgraph | ||
xsound | ||
dl |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it really needed? We use static linkage (at least in rust part I seen staticlib crate)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I should check this, but I remember that I get symbol not found
on linux without -ldl
20a840d
to
619b4a4
Compare
Added vange-rs renderer via Rust FFI. How to build:
ControlsOne can switch between old and new Rust renderer with |
6fd1fb9
to
ccf2dc2
Compare
|
|
||
void rv_map_update_data(rv_context context, rv_rect region); | ||
|
||
void rv_map_update_palette(rv_context context, int32_t first_entry, int32_t entry_count, uint8_t* palette); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking about passing uint8_t** lineT
to rv_map_update_data
, and remove it from MapDescription
. This way, the Rust side wouldn't keep the lineT pointer permanently. @kvark what do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep, that is fine
@kvark i'm thinking about new name for the |
The name seems fine. We could change to something like |
@kvark |
* AbstractCoreRenderer: SDL_Renderer replacement * AbstractRenderer: class for future GPU rendering implementations Rename
@lpenguin renamed in kvark/vange-rs@3d6ab75 |
Preparing for
vange-rs
integrationrenderer - a separate library subproject for 2D and 3D rendering
See: Vangers/lib/renderer.
Included libraries in the source tree
See Vangers/lib/renderer/src/renderer/lib:
Additional dependences:
Main classes
renderer::core::AbstractCoreRenderer
: SDL_Renderer replacementrenderer::core::SokolCoreRenderer
: SDL_Renderer replacement implementation with sokol_gfx librenderer::scene::AbstractRenderer
: a class for future GPU rendering implementationsrenderer::scene::SokolRenderer
: a simple GPU renderer with sokol_gfx librenderer::ResourceId<T>
a resource identifier which is basically a unique integral type. Each resource type mush typedef its own ResourceId. Seerenderer::core::Texture
resource identifier as an example.renderer::core::Texture
: a texture identifierrenderer::scene::HeightMap
: a height map identifierrenderer::scene::util::MapUpdater
: an utility class forHeightMap
updatesxgraph changes
Since the
SDL_Renderer
is not working with OpenGL in parallel, I decided to create our own 2D rendering mini-framework and replaceSDL_Renderer
andSDL_Texture
withrenderer::core::AbstractCoreRenderer
andrenderer::core::Texture
. Only theese two classes were replaced, we still useSDL_Surface
for loading the BMP and pixel rendering.Original frame rendering changes (iGameMap, vrtMap)
Nothing much was changed: the software rendering into the
lineTcolor
is still here, but the actual screen rendering was replaced with the new AbstractRendering class. I didn't disable the software renderer completely, because it's hard to isolate and I decided to focus onvange-rs
integration at first.Known problems
renderer
word is not perfect, because it may conflict with variable names or headers. Consider more unique name likexrender
?renderer::core
namespace is confusing - it should be probably used for core types, so we should move 2D rendering classes torenderer::2d
namespace or somethingrenderer::ResourceId<T>
is ugly, because it uses temporary enums for declaring the new type:struct HeightMap {int32_t id;}
or useResourceId
as base class without templates.sokol_gfx
andHandmadeMath
libraries directly into the code. We could get rid ofHandmadeMath
and use our own3d_math.h
library. Need to decide how ship or installsokol_gfx
.renderer::scene::SokolRenderer
uses full texture updates instead of partial ones - thesokol_gfx
library doesn't support partial updates unfortunately. It should not be a big problem since this class was created as dummy class, and the real GPU renderer should bevange-rs
renderer.AbstractRenderer::render
function doesn't support camera rotation and seems to be bloated with too many arguments. I suggest we could create theCamera
resource type and pass it to theAbstractRenderer::render